package com.test.demo1.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@RequestMapping("stock")
@RestController
@Slf4j
public class RedisDemoController {
    private final StringRedisTemplate redisTemplate;
    private final RedissonClient redisson;

    public RedisDemoController(StringRedisTemplate redisTemplate, RedissonClient redisson) {
        this.redisTemplate = redisTemplate;
        this.redisson = redisson;
    }
    private  Map<String, Integer> map = new HashMap();
    {
        map.put("productId::1", 100);
    }
    @RequestMapping("del")
    public void delStock(){
        //TODO: 删除库存
/**
 * Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", "1");
 * redisTemplate.expire("productId::1", 10, TimeUnit.SECONDS);
 * 此代码可能出现的问题 执行了第一条语句，系统宕机，导致锁无法释放，形成死锁
 * 解决方案：
 * 保证两条语句的原子性，可以采用以下方式
 * Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", "1",10,TimeUnit.SECONDS);
 *此代码可能会出现问题，在多线程环境下，线程1执行Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", "1",10,TimeUnit.SECONDS); 花费的时间超过10秒，线程2执行Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", "1",10,TimeUnit.SECONDS);
 * 线程1执行redisTemplate.delete("productId::1");，释放线程2的锁。导致其它线程能获取锁，造成超卖问题
 * 解决方案：加入当前时间戳/uuid/雪花id+线程id，作为锁的值，如果锁的值和当前时间戳一致，则删除锁，否则不删除锁
 * String currentTimeMillis = String.valueOf(System.currentTimeMillis());
 *         Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", currentTimeMillis,10,TimeUnit.SECONDS);
 *         try {
 *             if (b) {
 *                 redisTemplate.expire("productId::1", 10, TimeUnit.SECONDS);
 *                 Integer stock = (Integer) map.get("productId::1");
 *                 if (stock > 0) {
 *                     map.put("productId::1", stock - 1);
 *                 }
 *             }
 *         } finally {
 *         //是当前线程加的锁，才去释放
 *             if (currentTimeMillis.equals(redisTemplate.opsForValue().get("productId::1"))) {
 *                 redisTemplate.delete("productId::1");
 *             }
 *         }
 *  此代码可能出现的问题 也需要考虑原子性，如果获取到执行删除时出现卡顿，此时刚好锁到期释放，仍然可能出现删除其它线程锁的问题
 *  if (currentTimeMillis.equals(redisTemplate.opsForValue().get("productId::1"))) {
 *  *                 redisTemplate.delete("productId::1");
 *  *             }
 *  解决方案：使用lua脚本保证原子性
 *         String currentTimeMillis = UUID.randomUUID().toString() + ":" + Thread.currentThread().getId();
 *         Boolean b = redisTemplate.opsForValue().setIfAbsent("productId::1", currentTimeMillis,10,TimeUnit.SECONDS);
 *         try {
 *             if (b) {
 *                 redisTemplate.expire("productId::1", 10, TimeUnit.SECONDS);
 *                 Integer stock = (Integer) map.get("productId::1");
 *                 if (stock > 0) {
 *                     map.put("productId::1", stock - 1);
 *                 }
 *             }
 *         } finally {
 *             Long result = redisTemplate.execute(
 *                     new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then " +
 *                             "   return redis.call('del', KEYS[1]) " +
 *                             "else " +
 *                             "   return 0 " +
 *                             "end", Long.class),
 *                     Collections.singletonList("productId::1"),
 *                     currentTimeMillis
 *             );
 *             if (result != null && result == 1) {
 *                 System.out.println("锁释放成功");
 *             }
 *         }
 *
 *         此时代码仍可能出现线程1执行15秒。此时锁在第10秒已自动过期，线程2拿到锁，线程2执行了4s，形成超卖
 *         解决方案：增加守护线程，守护线程每5秒执行一次，判断主线程是否执行完毕，如果未执行完毕，则进行锁续命
 *   // 1. 生成唯一锁标识（避免误删其他线程的锁）
 * String lockKey = "productId::1";
 * String lockId = UUID.randomUUID() + ":" + Thread.currentThread().getId();
 *
 * // 2. 尝试获取锁（原子操作：SET lockId NX EX 10）
 * Boolean locked = redisTemplate.opsForValue()
 *     .setIfAbsent(lockKey, lockId, 10, TimeUnit.SECONDS);
 *
 * // 3. 续期控制标志（守护线程启停开关）
 * AtomicBoolean keepRenewing = new AtomicBoolean(false);
 * Thread renewThread = null;
 *
 * try {
 *     if (locked != null && locked) {
 *         // 4. 启动守护线程（续期核心）
 *         keepRenewing.set(true);
 *         renewThread = new Thread(() -> {
 *             while (keepRenewing.get()) {
 *                 try {
 *                     // 每隔5秒续期一次（小于锁超时时间）
 *                     Thread.sleep(5000);
 *                     // 原子续期：仅当锁仍属于当前线程才延长
 *                     String script =
 *                         "if redis.call('get', KEYS[1]) == ARGV[1] then " +
 *                         "   return redis.call('expire', KEYS[1], ARGV[2]) " +
 *                         "else " +
 *                         "   return 0 " +
 *                         "end";
 *                     redisTemplate.execute(
 *                         new DefaultRedisScript<>(script, Long.class),
 *                         Collections.singletonList(lockKey),
 *                         lockId, "10" // 续期10秒
 *                     );
 *                 } catch (InterruptedException e) {
 *                     Thread.currentThread().interrupt();
 *                 }
 *             }
 *         });
 *         renewThread.setDaemon(true); // 标记为守护线程[1,2](@ref)
 *         renewThread.start();
 *
 *         // 5. 执行业务逻辑
 *         Integer stock = (Integer) map.get(lockKey);
 *         if (stock > 0) {
 *             map.put(lockKey, stock - 1); // 扣减库存
 *             Thread.sleep(15000); // 模拟超长业务（超过初始10秒）
 *         }
 *     }
 * } finally {
 *     // 6. 释放资源：先停续期线程，再删锁
 *     keepRenewing.set(false); // 立即停止续期
 *     if (renewThread != null) {
 *         renewThread.join(1000); // 等待续期线程退出
 *     }
 *
 *     // 7. 原子释放锁（Lua脚本保证校验+删除原子性）
 *     String delScript =
 *         "if redis.call('get', KEYS[1]) == ARGV[1] then " +
 *         "   return redis.call('del', KEYS[1]) " +
 *         "else " +
 *         "   return 0 " +
 *         "end";
 *     Long result = redisTemplate.execute(
 *         new DefaultRedisScript<>(delScript, Long.class),
 *         Collections.singletonList(lockKey),
 *         lockId
 *     );
 *     if (result != null && result == 1) {
 *         log.info("锁释放成功");
 *     }
 * }
 *
 *  上述代码redisson均已提供成熟的解决方案，以下是redisson实现
 *           RLock lock = redisson.getLock("product:lock:1"); // 获取锁实例
 *         boolean locked = false;
 *         try {
 *             locked = lock.tryLock(10, 5, TimeUnit.SECONDS);
 *             if (locked) {
 *                     log.info("成功获取锁，执行业务逻辑...");
 *                     // 模拟业务操作（如库存扣减）
 *                     Integer stock =  map.get("productId::1");
 *                     if (stock>0){
 *                         map.put("productId::1",stock-1);
 *                     }
 *             } else {
 *                 log.info("未获取锁，执行降级策略");
 *             }
 *         } catch (InterruptedException e) {
 *             Thread.currentThread().interrupt(); // 恢复中断标志
 *             throw new RuntimeException("锁获取被中断", e);
 *         } finally {
 *            if (locked&&lock.isHeldByCurrentThread()){
 *                lock.unlock();
 *            }
 *         }
 *     }
 */
        RLock lock = redisson.getLock("product:lock:1"); // 获取锁实例
        boolean locked = false;
        try {
            locked = lock.tryLock(10, 5, TimeUnit.SECONDS);
            if (locked) {
                    log.info("成功获取锁，执行业务逻辑...");
                    // 模拟业务操作（如库存扣减）
                    Integer stock =  map.get("productId::1");
                    if (stock>0){
                        map.put("productId::1",stock-1);
                    }
            } else {
                log.info("未获取锁，执行降级策略");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断标志
            throw new RuntimeException("锁获取被中断", e);
        } finally {
           if (locked&&lock.isHeldByCurrentThread()){
               lock.unlock();
           }
        }
    }
}
